package wiiusej.apptest;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;

import org.apache.log4j.FileAppender;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.Priority;
import org.apache.log4j.PropertyConfigurator;

import wiiusej.Wiimote;
import wiiusej.apptest.statistics.AccelerationPaquet;
import wiiusej.apptest.statistics.PacketController;
import wiiusej.apptest.statistics.PacketManager;
import wiiusej.apptest.statistics.PacketWraped;
import wiiusej.apptest.statistics.performance.PerformanceUnit;
import wiiusej.apptest.statistics.performance.Track;
import wiiusej.motionAlgorithms.Centroid;
import wiiusej.motionAlgorithms.CentroidsEnum;
import wiiusej.motionAlgorithms.Kmeans;
import wiiusej.motionAlgorithms.KmeansImpl;
import wiiusej.motionAlgorithms.PacketParametersCalculator;
import wiiusej.wiiusejevents.GenericEvent;
import wiiusej.wiiusejevents.physicalevents.MotionSensingEvent;
import measurement.main.WiimoteMeasurementListener;

public class WiimoteKmeansListener extends WiimoteMeasurementListener{

	//Logger
	private static Logger fileLogger;
	private static Logger performanceLogger;
	private static final String fileURL = 
		"src/test/java/wiiusej/apptest/statistics/Kmeans/KmeansPerformanceFromListener";
	
	private static long id;
	//STATE MACHINE
	private boolean run = false;
	private CentroidsEnum centroidState;
	
	public enum ListenerStateMachine {	
		STOP, CENTROID_RUN,GAME_RUN;
	}	
	private ListenerStateMachine listenerState = ListenerStateMachine.STOP;
	private boolean firstPacketRemoved = false;
	
	//COM QUEUES
	private static BlockingQueue<CentroidsEnum> calibrationKmeans = new LinkedBlockingDeque<CentroidsEnum>();
	private BlockingQueue<Track> toPerformanceController;
	//protected BlockingQueue<Vector<Centroid>> toRecognitionListener;
	
	
	//STATE MACHINE
	private final int STOP = 0;
	private final int START = 1;	

	//private Vector<AccelerationPaquet> myEventVector;
	private PacketParametersCalculator parameterCalculator = new PacketParametersCalculator();
	
	//kmeans algorithm centroids
	private CentroidsEnum centroidsType;
	protected Vector<Centroid> centroids = new Vector<Centroid>();
	private KmeansImpl kmeansController;
	
	//counters
	private int packetSize, lastSize = 0;
	
	//FACTORS
	private final double MILLI_FACTOR = 1.0E06;
	
	//PacketController
	private PacketController pController;
	
	//OTHERS
	SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
	private String pattern = "000,000 ";
	DecimalFormat myFormatter = new DecimalFormat(pattern);
	
	//Packets that are needed to measure the performance of k-Means
	private long measurementPerformance = 0;
	private final long threshold = 100;
	

	@SuppressWarnings("static-access")
	public WiimoteKmeansListener(ExecutorService exe, Wiimote aux,
			String measurementLength, BlockingQueue<CentroidsEnum> calibrationKmeans, BlockingQueue<Track> fromSystem) {
		
		super(exe, aux, measurementLength);
		
		this.id = this.wiimote.getId();
		this.kmeansController =  new KmeansImpl(this.id);
		
		//COM QUEUES
		this.calibrationKmeans = calibrationKmeans;
		this.toPerformanceController = fromSystem;
			
		initLoggers();
		
		this.pController = new PacketController(this.wiimote.getId(), 
				randomID, toPerformanceController);
		
		performanceLogger.info("INIT PERFORMANCE LOGGER FROM LISTENER");
		
		registerListeners();		
		activateMotionSensing();
		initCentroids();
	}
	
	@Override
	public void registerListeners(){
		fileLogger.debug("Registering Listener in Wiimote: " + wiimote.getId());
		wiimote.addWiiMoteEventListeners(this);		
	}
	
	
	private void initLoggers(){
		
		//LOGGER
		PropertyConfigurator.configure("log4j.properties");
		fileLogger = Logger.getLogger("kmeanListener");		
		performanceLogger = Logger.getLogger("kmeansPerformanceListener");
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void onMotionSensingEvent(MotionSensingEvent e, long packetRefNumber, Timestamp packetTime) {
		
		//fileLogger.info("");
		//fileLogger.info("******************** NEW PACKET ********************");
		//fileLogger.info("");
		
		
		switch(listenerState){
		
		case CENTROID_RUN:
				
			packetSize = getMyPackets().getMyEventVector().getPackets().size();
			
			fileLogger.debug(" - onMotionSensingEvent - CurrentSize: " + packetSize);
			fileLogger.debug("");
						
			if(packetSize >= measurementLength){				
				stopListener();			
				calculeCentroid();							
			}else{			
				getPacketStatistics(e, packetTime);				
			}			
			break;
			
		case STOP:			
			//fileLogger.debug("I DON'T WANT TO WORK :)");
			//fileLogger.debug("AnalyzedPackets: " + measurementPerformance);			
			break;
			
		case GAME_RUN:
			
			fileLogger.debug(" - onMotionSensingEvent - CurrentSize: " + packetSize);
					
			
			if((measurementPerformance >= threshold)){
				listenerState = ListenerStateMachine.STOP;				
				fileLogger.info("LISTENER STOPED - PERFORMANCE PERIOD IS OVER");
				fileLogger.info("Analyzed Packets: " + measurementPerformance);
				break;				
			}
			
			if( (packetSize > lastSize)) {
				
				fileLogger.debug("Sending new Packet to Kmeans" + " CurrentSize: " + 
						packetSize + " LastSize: " + lastSize);
				
				long start = System.nanoTime();
				setCentroids((Vector<Centroid>) sendToKmeans());			
				
				long end = System.nanoTime();
				//performanceLogger.info(myFormatter.format((end-start)/1.0E06));
				performanceLogger.info((end-start)/MILLI_FACTOR);
				
				
				fileLogger.info("Performance: " + (end-start)/MILLI_FACTOR);
				
				getMyPackets().getMyEventVector().getPackets().clear();			
				
				packetSize = getMyPackets().getMyEventVector().getPackets().size();
				lastSize = getMyPackets().getMyEventVector().getPackets().size();
				
				fileLogger.debug("CurrentSize: " + packetSize + " LastSize: " + lastSize + 
						" myPacketEventVector?: " + getMyPackets().getMyEventVector().getPackets().isEmpty()
						+ " MyPacketEventVectorSize: " + 
						getMyPackets().getMyEventVector().getPackets().size());
				
				fileLogger.debug("");
			}else{
				
				getPacketStatistics(e, packetTime);
				
				fileLogger.debug("");
				
				if(!getMyPackets().getMyEventVector().getPackets().isEmpty()){
					for(AccelerationPaquet aux : getMyPackets().getMyEventVector().getPackets()){
						fileLogger.debug("Time: " + tsFormat.format(aux.getTime()) + " #: " + aux.getCounter());
					}				
				}else{
					fileLogger.debug("myEventVector EMPTY!!");					
				}
				
				//upload current packet size
				packetSize = getMyPackets().getMyEventVector().getPackets().size();
				fileLogger.debug("EventVectorSize: " + getMyPackets().getMyEventVector().getPackets().size() + 
						" PacketSize: " + packetSize + " LastSize: " + lastSize);				
				
				fileLogger.debug("");				
				
			}			
			break;
			
			default:				
				fileLogger.info("FATAL STATE MACHINE ERROR!!");		
		}		
		
	}
	
	

	private void calculeCentroid() {
		
		fileLogger.debug("");
		fileLogger.debug("CALCULING CENTROIDS FUNCTION: ");
		fileLogger.debug("");
		
		//we need to calcule the packet parameters
		parameterCalculator.calculeMotionParameters(getMyPackets().getMyEventVector().getPackets());
		
		//now we have to add the events to the centroid and compute a new center
		Centroid centroid;
		
		if((centroid = getCentroidType(centroidState)) != null){
			centroid.setMotionSamples
				((Vector<AccelerationPaquet>) getMyPackets().getMyEventVector().getPackets().clone());
			
			centroid.computeNewCenter();
			
		}else{					
			fileLogger.error("Centroid NOT found -- System FAILURE!!");
		}
		
		if(centroids != null){
			fileLogger.info("Centroids from PacketParameters: ");
			for(Centroid aux : getCentroids()){
				fileLogger.info(aux);		
				fileLogger.info("MotionSamplesSize : " + aux.getMotionSamples().size());
				//fileLogger.info("MotionSamples: " + aux.getMotionSamples());
			}							
		}else{					
			fileLogger.error("ALGORITHM FAILURE!!!");
		}
		
		//Clear the structure
		getMyPackets().getMyEventVector().getPackets().clear();
		getMyPackets().clearTemporal();
		
		//upload counters
		packetSize = getMyPackets().getMyEventVector().getPackets().size();
		lastSize = packetSize;
		
		fileLogger.debug("");
		fileLogger.info("Clearing EventVector: " + getMyPackets().getMyEventVector().getPackets().size()
				 + " Temporal: " + getMyPackets().getTemporal().getTime());
		fileLogger.debug("");
		
		
		//In case the mode was k-means check (4). Otherwise the mode is 5, which means that there is no system, because
		//this listener is created by another listener instead a Wiimote
		if(calibrationKmeans!=null){
			sendToSystem();
		}
		
		
		/*
		//We need to send start signal to Calibration and wait for answer
		try {
			calibrationKmeans.put(centroidState);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}*/
		
	}
	
	private void sendToSystem(){
		
		//We need to send start signal to Calibration and wait for answer
		try {
			calibrationKmeans.put(centroidState);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
	}
	
	private void clearCentroidSamples(){
		
		for(Centroid c : centroids){		
			c.getMotionSamples().clear();
			fileLogger.debug("Centroid: " + c.getInitType());
			fileLogger.debug("MotionSamples: " + c.getMotionSamples());
		}
		
	}

	private Centroid getCentroidType(CentroidsEnum centroidsType) {		
		//search for the required centroid type
		for(Centroid aux : getCentroids()){
			if(aux.getInitType() == centroidsType){
				return aux;				
			}
		}		
		return null;
	}

	@SuppressWarnings("unchecked")
	private void getPacketStatistics(MotionSensingEvent e, Timestamp packetTime){
		
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		fileLogger.debug(" - GetPacketsStats - MyEventVector Size: " + 
				getMyPackets().getMyEventVector().getPackets().size()); 
		
		for(AccelerationPaquet aux : getMyPackets().getMyEventVector().getPackets()){
			fileLogger.debug("PacketTime: " + tsFormat.format(aux.getTime()) + " Packets: " + aux.getCounter());			
		}
		
		if(getMyPackets().getTemporal() != null){
			fileLogger.debug(" - GetPacketsStats - Temporal: " + 
					getMyPackets().getTemporal().getCounter());
		}else{
			fileLogger.debug(" - GetPacketsStats - Temporal: Null");
		}
				
		
		fileLogger.debug(" -- myPackets -- Before PacketManager - Temporal: " + 
				getMyPackets().getTemporal().getCounter() + " myPacketVector: " + 
				getMyPackets().getMyEventVector().getPackets().size());
		
		this.myPackets = pController.makeChunks(e, myPackets, packetTime);
		
		//setMyPackets(myPackets);
		
		fileLogger.debug(" -- myPackets -- After PacketManager - Temporal: " + 
				getMyPackets().getTemporal().getCounter() + " myPacketVector: " + 
				getMyPackets().getMyEventVector().getPackets().size());
		
		
		if(!getMyPackets().getMyEventVector().getPackets().isEmpty()){
			for(AccelerationPaquet aux : getMyPackets().getMyEventVector().getPackets()){
				fileLogger.debug("Time: " + tsFormat.format(aux.getTime()) + " #: " + aux.getCounter());
			}				
		}
		
		/*
		Future future = 
			exe.submit(new PacketManager(e, wiimote.getId(), randomID, getMyPackets(), null, null));
				
		try {			
			fileLogger.debug(" -- myPackets -- Before PacketManager - Temporal: " + 
					getMyPackets().getTemporal().getCounter() + " myPacketVector: " + 
					getMyPackets().getMyEventVector().getPackets().size());
			
			PacketWraped uploadPackets = (PacketWraped) future.get();
			setMyPackets(uploadPackets);
			
			
			fileLogger.debug(" -- myPackets -- After PacketManager - Temporal: " + 
					getMyPackets().getTemporal().getCounter() + " myPacketVector: " + 
					getMyPackets().getMyEventVector().getPackets().size());			
			
			if(!getMyPackets().getMyEventVector().getPackets().isEmpty()){
				for(AccelerationPaquet aux : getMyPackets().getMyEventVector().getPackets()){
					fileLogger.debug("Time: " + tsFormat.format(aux.getTime()) + " #: " + aux.getCounter());
				}				
			}
			
					
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		} catch (ExecutionException e1) {
			e1.printStackTrace();
		}		*/
		
		
	}
	
	@SuppressWarnings("unchecked")
	private Vector<Centroid> sendToKmeans(){		
		
		SimpleDateFormat tsFormat = new SimpleDateFormat("mm:ss:SS");
		
		fileLogger.debug(" - SendPacketsKmean - MyEventVector Size: " + 
				getMyPackets().getMyEventVector().getPackets().size());
		
		for(AccelerationPaquet aux : getMyPackets().getMyEventVector().getPackets()){
			fileLogger.debug(" - SendPacketsKmean - PacketTime: " + tsFormat.format(aux.getTime()) 
					+ " Packets: " + aux.getCounter());			
		}
						
		if(getMyPackets().getTemporal() != null){
			fileLogger.debug(" - SendPacketsKmean - Temporal: " + 
				getMyPackets().getTemporal().getCounter());
		}else{
			fileLogger.debug(" - SendPacketsKmean - Temporal: Null");
		}

		
		Vector<Centroid> centers = kmeansController.computeMotion((Vector<AccelerationPaquet>) 
				getMyPackets().getMyEventVector().getPackets().clone(),	
				(Vector<Centroid>) getCentroids().clone());
		
		
		measurementPerformance++;
		
		for(Centroid c : getCentroids()){
			fileLogger.debug("Centroid: " + c.getInitType() + 
					" MotionSamplesSize: " + c.getMotionSamples().size());
			fileLogger.debug("MotionSamples: " + c.getMotionSamples());
		}
		
		return centers;		
	}
	
	@SuppressWarnings("static-access")
	private void initCentroids() {		
		Centroid slowCentroid = new Centroid(centroidsType.SLOW);
		Centroid bigCentroid = new Centroid(centroidsType.BIG);
		Centroid roboticCentroid = new Centroid(centroidsType.ROBOTIC);
		
		centroids.add(slowCentroid);
		centroids.add(bigCentroid);
		centroids.add(roboticCentroid);
		
		fileLogger.debug("");
		fileLogger.debug("Adding Centroids:");
		for(Centroid c : centroids){
			fileLogger.debug(c.toString());	
			fileLogger.debug(c.getMotionSamples());
		}
		
	}
	
	
	
	public synchronized void startListener(CentroidsEnum type){
		fileLogger.debug("Listener Started");
		this.run=true;		
		this.listenerState = ListenerStateMachine.CENTROID_RUN;
		this.centroidState = type;
	}
	
	public synchronized void stopListener(){
		fileLogger.debug("Listener Stoped");
		this.listenerState = ListenerStateMachine.STOP;
		this.run=false;		
	}
	
	public synchronized boolean getSM(){
		return this.run;
	}

	
	public synchronized Vector<Centroid> getCentroids() {
		return centroids;
	}

	public synchronized void setCentroids(Vector<Centroid> centroids) {
		this.centroids = (Vector<Centroid>) centroids.clone();
	}

	public void startGame() {
		this.listenerState = ListenerStateMachine.GAME_RUN;
		this.centroidState = null;
		
		fileLogger.debug("");
		fileLogger.debug("STARTING THE GAME!!");
		fileLogger.debug("");
	}
	
	

}
